﻿using AgorApp.Roma.Model.DataTransferObjects;
using AgorApp.Roma.Model;
using AgorApp.Roma.ServiceContracts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using AutoMapper.Mappers;
using AgorApp.Roma.Oracle.Services.Mappers;
using Newtonsoft.Json;
using System.Data.Entity;

namespace AgorApp.Roma.Oracle.Services
{
    public class AssembleeCapitolineService : IAssembleeCapitolineService
    {
        public ITagService TagService { get; private set; }

        private AgorAppRomaContext dbContext;
        public AssembleeCapitolineService(ITagService tagService)
        {
            this.TagService = tagService;
            dbContext = new AgorAppRomaContext();
            MapperConfigure();
        }

        #region AutoMapper

        private ConfigurationStore config;
        private MappingEngine mapper;
        private void MapperConfigure()
        {
            config = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
            config.AssertConfigurationIsValid();
            mapper = new MappingEngine(config);
            config.AddProfile<DataTransferObjectsProfile>();
            config.AddProfile<AssembleeCapitolineServiceProfile>();
            mapper.ConfigurationProvider.AssertConfigurationIsValid();
        }

        private class AssembleeCapitolineServiceProfile : Profile
        {
            public override string ProfileName
            {
                get
                {
                    return this.GetType().Name;
                }
            }

            protected override void Configure()
            {
                base.Configure();

                SourceMemberNamingConvention = new PascalCaseNamingConvention();
                DestinationMemberNamingConvention = new UpperUnderscoreNamingConvention();

                CreateMap<AssembleeCapitoline, ASSEMBLEE_CAPITOLINE>()
                    .ForMember(dest => dest.TESTO_ODG, opt => opt.ResolveUsing((AssembleeCapitoline ass) =>
                    {
                        return JsonConvert.SerializeObject(ass.TestoOdG);
                    }));

                CreateMap<Tag, TAG>()
                    .ForMember(dest => dest.TAG1, opt => opt.MapFrom(src => src.TagName))
                    .ForMember(dest => dest.ID_TIPO_TAG, opt => opt.MapFrom(src => (short)src.TagType))
                    .ForMember(dest => dest.TAG_TYPE, opt => opt.Ignore())
                    .ForMember(dest => dest.GRUPPI_CONSILIARI, opt => opt.Ignore())
                    .ForMember(dest => dest.CONSIGLIERI_ASSEMBLEA, opt => opt.Ignore())
                    .ForMember(dest => dest.DELIBERE_ASSEMBLEA_CAPITOLINA, opt => opt.Ignore())
                    .ForMember(dest => dest.NEWS, opt => opt.Ignore());
            }
        }

        #endregion

        #region CRUD Operations

        #region Direct Methods

        public ICollection<AssembleeCapitoline> GetAll()
        {
            var listaAssembleeEntity = dbContext.ASSEMBLEE_CAPITOLINE
                .Include(src => src.TAGS);

            var listaAssemblee = mapper.Map<ICollection<AssembleeCapitoline>>(listaAssembleeEntity);
            return listaAssemblee;
        }

        public AssembleeCapitoline Get(int key)
        {
            var assembleeEntity = dbContext.ASSEMBLEE_CAPITOLINE.Find(key);
            if (assembleeEntity == null) return null;
            dbContext.Entry(assembleeEntity).Collection<TAG>(src => src.TAGS).Load();
            return mapper.Map<AssembleeCapitoline>(assembleeEntity);
        }

        public void Insert(AssembleeCapitoline entity)
        {
            var ent = dbContext.ASSEMBLEE_CAPITOLINE.Find(entity.IdAssemblea);
            if (ent != null)
                throw new InvalidOperationException("Entità esistente!");
            //trasforma il DTO in ORM entity
            var assemb = mapper.Map<AssembleeCapitoline, ASSEMBLEE_CAPITOLINE>(entity);
            //verifica i tag già esistenti sostituendo quelli passati
            var tags = assemb.TAGS.ToList();
            foreach(var tag in tags)
            {
                var t = dbContext.TAGS.SingleOrDefault(x =>
                    x.TAG1.Equals(tag.TAG1) && x.ID_TIPO_TAG == tag.ID_TIPO_TAG);
                if (t != null)
                {
                    assemb.TAGS.Remove(tag);
                    assemb.TAGS.Add(t);
                }
            };
            dbContext.ASSEMBLEE_CAPITOLINE.Add(assemb);
            dbContext.SaveChanges();
            //per aggiornare il DTO
            mapper.Map<ASSEMBLEE_CAPITOLINE, AssembleeCapitoline>(assemb, entity);
        }

        public void Update(AssembleeCapitoline entity)
        {
            var ent = dbContext.ASSEMBLEE_CAPITOLINE.Find(entity.IdAssemblea);
            if (ent == null)
                throw new InvalidOperationException("Entità non esistente");
            //rimuovo i tags nel context
            ent.TAGS.Clear();
            //mapping with new values
            ent = mapper.Map<AssembleeCapitoline, ASSEMBLEE_CAPITOLINE>(entity, ent);
            //tags della mappatura
            var tags = ent.TAGS.ToList();
            //rimuovo tutti i tag per rimapparli
            ent.TAGS.Clear();
            foreach (var tag in tags)
            {
                var t = dbContext.TAGS.SingleOrDefault(x =>
                    x.TAG1.Equals(tag.TAG1) && x.ID_TIPO_TAG == tag.ID_TIPO_TAG);
                if (t != null) //se esiste lo aggiunge all'entità
                    ent.TAGS.Add(t);
                else //altrimenti lo crea nuovo
                {
                    tag.ID_TAG = -1;
                    ent.TAGS.Add(tag);
                }
            }
            dbContext.SaveChanges();
            //Rimappo il DTO
            entity = mapper.Map<ASSEMBLEE_CAPITOLINE, AssembleeCapitoline>(ent, entity);
        }

        public void Delete(AssembleeCapitoline entity)
        {
            var ent = dbContext.ASSEMBLEE_CAPITOLINE.Find(entity.IdAssemblea);
            if (ent == null)
                throw new InvalidOperationException("Entità inesistente");

            dbContext.ASSEMBLEE_CAPITOLINE.Remove(ent);
            dbContext.SaveChanges();
        }

        #endregion

        #region Async methods

        public async Task<ICollection<AssembleeCapitoline>> GetAllAsync()
        {
            return await Task.Run<ICollection<AssembleeCapitoline>>(() =>
                GetAll());
        }

        public async Task<AssembleeCapitoline> GetAsync(int key)
        {
            return await Task.Run<AssembleeCapitoline>(() =>
                Get(key));
        }

        public async Task InsertAsync(AssembleeCapitoline entity)
        {
            await Task.Run(() => Insert(entity));
        }

        public async Task UpdateAsync(AssembleeCapitoline entity)
        {
            await Task.Run(() => Update(entity));
        }

        public async Task DeleteAsync(AssembleeCapitoline entity)
        {
            await Task.Run(() => Delete(entity));
        }
        
        #endregion        
        
        #endregion

        #region IDisposable

        private bool disposed = false;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disponsing)
        {
            if (!disposed)
            {
                if (disponsing)
                {
                    dbContext.Dispose();
                    mapper.Dispose();
                }
            }
            disposed = true;
        }
        #endregion
    }
}